<?php
/**
 * Copyright (c) [2019] [吴跃忠]
 * [selibra] is licensed under the Mulan PSL v1.
 * You can use this software according to the terms and conditions of the Mulan PSL v1.
 * You may obtain a copy of Mulan PSL v1 at:
 * http://license.coscl.org.cn/MulanPSL
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v1 for more details.
 */

namespace ckeeper\utils;


use Selibra\Di\DI;
use Selibra\Redis\RedisClient;

class Lock
{
    /**
     * @var RedisClient
     */
    private RedisClient $redis;

    /**
     * @var bool
     */
    private bool $coroutine = true;

    /**
     * Lock constructor.
     * @param string $key 锁名称
     * @param bool $coroutine 是否受协程控制
     */
    public function __construct(private string $key, $coroutine = true)
    {
        $this->coroutine = $coroutine;
    }


    /**
     * 上锁
     * @param bool $wait
     * @return bool
     */
    public function lockup($wait = true): bool
    {
        $key = 'R_LOCK:' . $this->key;
        if (!$wait) {
            do {
                if (empty($this->redis()->get($key))) {
                    break;
                }
            } while (true);
        } else {
            // 如果不需要等待，直接返回已上锁
            if (!empty($this->redis()->get($key))) {
                return false;
            }
        }
        // 设置锁定时间
        $lockTime = ini_get('max_execution_time') ?: 300;
        $this->redis()->set($key, 1, ['nx', 'ex' => $lockTime]);
        if ($this->coroutine) {
            defer(function () {
                // 协程结束，自动解锁
                $this->unlock();
            });
        }
        return true;
    }


    /**
     * 开锁
     * @return void
     */
    public function unlock(): void
    {
        $key = 'R_LOCK:' . $this->key;
        $this->redis()->del($key);
    }


    /**
     * @return RedisClient
     */
    private function redis(): RedisClient
    {
        if (!isset($this->redis)) {
            /** @var RedisClient redis */
            $this->redis = DI::getObjectContext(RedisClient::class);
        }
        return $this->redis;
    }
}
